package me.aartikov.alligator.screenswitchers;


import me.aartikov.alligator.animations.AnimationData;
import me.aartikov.alligator.animations.TransitionAnimation;
import me.aartikov.alligator.destinations.Destination;
import me.aartikov.alligator.destinations.FragmentDestination;
import me.aartikov.alligator.exceptions.NavigationException;
import me.aartikov.alligator.exceptions.ScreenRegistrationException;
import me.aartikov.alligator.helpers.FractionCache;
import me.aartikov.alligator.helpers.FractionStack;
import me.aartikov.alligator.listeners.ScreenSwitchingListener;
import me.aartikov.alligator.Screen;
import me.aartikov.alligator.ScreenResolver;
import me.aartikov.alligator.helpers.MiFraction;
import me.aartikov.alligator.helpers.FractionSwitcher;
import me.aartikov.alligator.navigationfactories.NavigationFactory;
import me.aartikov.alligator.util.Log;
import ohos.aafwk.ability.fraction.Fraction;
import ohos.aafwk.ability.fraction.FractionManager;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


/**
 * Screen switcher that switches fragments in a container. It uses {@link NavigationFactory} to create fragments and get screens back from it.
 * Screens used by {@code FragmentScreenSwitcher} must have {@code equals} and {@code hashCode} correctly overridden.
 */
public class FragmentScreenSwitcher implements ScreenSwitcher {
    public interface AnimationProvider {

        TransitionAnimation getAnimation(Screen screenFrom, Screen screenTo, AnimationData animationData);
    }

    private FractionSwitcher fragmentSwitcher;
    private NavigationFactory mNavigationFactory;
    private ScreenResolver mScreenResolver;
    private AnimationProvider mAnimationProvider;
    private FractionManager mFragmentManager;


    public AnimationProvider getmAnimationProvider() {
        return mAnimationProvider;
    }

    /**
     * @param navigationFactory navigation factory used to create fragments
     * @param fragmentManager   fragment manager used for fragment transactions
     * @param animationProvider animation provider
     */
    public FragmentScreenSwitcher(NavigationFactory navigationFactory, FractionManager fragmentManager, AnimationProvider animationProvider) {
        mNavigationFactory = navigationFactory;
        mScreenResolver = new ScreenResolver(mNavigationFactory);
        mFragmentManager = fragmentManager;
        Log.i("FragmentScreenSwitcher.FragmentScreenSwitcher([navigationFactory, fragmentManager, animationProvider]):57    "+fragmentManager);
        fragmentSwitcher = new FractionSwitcher(fragmentManager);
        mAnimationProvider = animationProvider;
        initFragmentMap();
    }

    /**
     * @param navigationFactory navigation factory used to create fragments
     * @param fragmentManager   fragment manager used for fragment transactions
     */
    public FragmentScreenSwitcher(NavigationFactory navigationFactory, FractionManager fragmentManager) {
        this(navigationFactory, fragmentManager, createDefaultAnimationProvider());
    }

    @Override
    public void switchTo(Screen screen, ScreenSwitchingListener listener, AnimationData animationData) throws NavigationException {
        if (checkScreenIsCurrent(screen)) {
            return;
        }

        Destination destination = mNavigationFactory.getDestination(screen.getClass());
        if (destination instanceof FragmentDestination) {
            MiFraction fragment = getOrCreateFragment(screen, (FragmentDestination) destination);
            MiFraction currentFragment = FractionStack.getInstance(mFragmentManager).getCurrentFragment(fragment);
            Screen currentScreen = null;
            if (currentFragment != null) {
                currentScreen = getScreen(currentFragment);
            }
            TransitionAnimation animation = mAnimationProvider.getAnimation(currentScreen, screen, animationData);

            fragmentSwitcher.switchTo(fragment, animation);
            listener.onScreenSwitched(currentScreen, screen);
        } else {
            throw new ScreenRegistrationException("Screen " + screen.getClass().getSimpleName() + " is not represented by a fragment.");
        }
    }

    private boolean checkScreenIsCurrent(Screen screen) {
        if (getCurrentFragment() == null) {
            return false;
        }
        return getCurrentFragment().getGouptag() == FractionCache.getInstance().get(screen);
    }


    @Override
    public void reduce(List<Screen> screens, ScreenSwitchingListener listener, AnimationData animationData) throws NavigationException {
        Screen screen = null;
        if (screens.size() > 0) {
            screen = screens.get(0);
        }

        if (checkScreenIsCurrent(screen)) {
            return;
        }

        Destination destination = mNavigationFactory.getDestination(screen.getClass());
        if (destination instanceof FragmentDestination) {
            List<MiFraction> fragment = getOrCreateFragment(screens, (FragmentDestination) destination);
            fragmentSwitcher.reduce(fragment);
            listener.onScreenSwitched(null, screen);
        } else {
            throw new ScreenRegistrationException("Screen " + screen.getClass().getSimpleName() + " is not represented by a fragment.");
        }
    }

    /**
     * Returns a current fragment.
     *
     * @return current fragment in the container, or {@code null} if there are no fragments in the container
     */

    public MiFraction getCurrentFragment() {
        return FractionStack.getInstance(mFragmentManager).getCurrentFragment();
    }


    private Screen getCurrentScreen() {
        Fraction currentFragment = getCurrentFragment();
        return currentFragment != null ? getScreen(currentFragment) : null;
    }

    private static AnimationProvider createDefaultAnimationProvider() {
        return new AnimationProvider() {
            @Override

            public TransitionAnimation getAnimation(Screen screenFrom, Screen screenTo, AnimationData animationData) {
                return TransitionAnimation.DEFAULT;
            }
        };
    }

    private void initFragmentMap() {
        List<MiFraction> fragmentList = FractionStack.getInstance(mFragmentManager).getStackFragment();
        for (MiFraction fragment : fragmentList) {
            FractionCache.getInstance().put(mScreenResolver.getScreen(fragment), fragment);
        }
    }

    private Screen getScreen(Fraction fragment) {
        for (Map.Entry<Screen, MiFraction> entry : FractionCache.getInstance().getFragmentCache().entrySet()) {
            if (entry.getValue() == fragment) {
                return entry.getKey();
            }
        }
        return null;
    }

    private MiFraction getOrCreateFragment(Screen screen, FragmentDestination destination) throws NavigationException {
        MiFraction fragment = FractionCache.getInstance().get(screen);
        if (fragment == null) {
            fragment = destination.createFragment(screen);
            FractionCache.getInstance().put(screen, fragment);
            try {
                mScreenResolver.getScreen(fragment);  // Check that the screen has a valid screen getting function
            } catch (Exception e) {
                throw new ScreenRegistrationException(e.getMessage());
            }
        }
        return fragment;
    }

    private List<MiFraction> getOrCreateFragment(List<Screen> screen, FragmentDestination destination) throws NavigationException {
        List<MiFraction> fragments = new ArrayList<>();
        for (int i = 0; i < screen.size(); i++) {
            MiFraction fragment = getOrCreateFragment(screen.get(i), destination);
            if (fragment != null) {
                fragments.add(fragment);
            }
        }
        return fragments;
    }
}
